﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Threading;

namespace ComplexAlloysVisualisation
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private List<Atom> atoms = new List<Atom>(2000); // very unlikely to have more than 2000 atoms, hence preallocation
        private AtomBuldier atomBuldier = new AtomBuldier();

        //masks for filtering special group of atoms
        private List<int> masks = new List<int>();

        private string fileName = "empty";
        private MouseHelper mouseHelper;
        private double radius = 1.0;

        public MainWindow()
        {
            InitializeComponent();
            mouseHelper = new MouseHelper(camera, visualModel, this);
            this.PreviewMouseWheel += new MouseWheelEventHandler(MainWindow_PreviewMouseWheel);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            Storyboard anim = (Storyboard)this.Resources["mainInAnimation"];
            anim.Begin();
        }

        // buggy zoom control
        private void MainWindow_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            e.Handled = true;
            var delta = e.Delta;
            if ((delta > 0) && (zoomControl.Zoom < 80.0))
            {
                zoomControl.Zoom += 0.05;
            }
            else if (zoomControl.Zoom > 0.05)
            {
                zoomControl.Zoom -= 0.05;
            }
        }

        private void openFileButton_Click(object sender, RoutedEventArgs e)
        {
            // WPF does not have his own dialog
            Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
            // Set filter for file extension and default file extension
            dialog.DefaultExt = ".txt";
            dialog.Filter = "Text documents (.chmc)|*.chmc";

            bool? result = dialog.ShowDialog();

            if (result.Value)
            {
                fileName = System.IO.Path.GetFileNameWithoutExtension(dialog.FileName);
                fileNameLabel.Content = fileName;
                ReadPosition(dialog.FileName);
                parseMasks();
                draw();
            }
        }

        private void ReadPosition(string filePath)
        {
            try
            {
                atoms.Clear();
                string[] lines = File.ReadAllLines(filePath);
                // skipping 4 lines of header
                for (int lineIndex = 4; lineIndex < lines.Length; ++lineIndex)
                {
                    // data is placed in a fixed offset manner
                    var parameters = lines[lineIndex].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                    var positionX = Double.Parse(parameters[0], CultureInfo.InvariantCulture);
                    var positionY = Double.Parse(parameters[1], CultureInfo.InvariantCulture);
                    var positionZ = Double.Parse(parameters[2], CultureInfo.InvariantCulture);
                    var unitCellLength = int.Parse(parameters[3], CultureInfo.InvariantCulture);
                    var positionNumber = int.Parse(parameters[4], CultureInfo.InvariantCulture);
                    var atomType = parameters[5];
                    var prob = Double.Parse(parameters[6], CultureInfo.InvariantCulture);
                    var vacProb = Double.Parse(parameters[7], CultureInfo.InvariantCulture);
                    var mgProb = Double.Parse(parameters[8], CultureInfo.InvariantCulture);
                    var alProb = Double.Parse(parameters[9], CultureInfo.InvariantCulture);

                    atoms.Add(new Atom()
                    {
                        X = positionX,
                        Y = positionY,
                        Z = positionZ,
                        UnitCellLength = unitCellLength,
                        PositionNumber = positionNumber,
                        AtomType = atomType.Trim(),
                        Prob = prob,
                        VacancyProbability = vacProb,
                        MagnesiumProbability = mgProb,
                        AluminiumProbability = alProb,
                    });
                    //Debug.WriteLine(positionXString + positionYString + positionZString + unitCellLengthString + positionNumberString + atomType + probString);
                    Debug.WriteLine(String.Format(" {0:0.00000} {1:0.00000} {2:0.00000} ", positionX, positionY, positionZ));
                }
                atoms.Sort((x, y) => x.VacancyProbability.CompareTo(y.VacancyProbability));
            }
            catch (IndexOutOfRangeException e)
            {
                MessageBox.Show("File " + filePath + " is corrupted." + Environment.NewLine + Environment.NewLine + e);
            }
        }

        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            camera.Position = new Point3D(0, 0, -30);
            mouseHelper.Reset();
            ScaleAtoms(1.0);
            zoomControl.Zoom = 1.0;
        }

        private void parseMasks()
        {
            masks.Clear();
            foreach (var item in checkBoxPanel.Children)
            {
                var checkBox = item as CheckBox;
                if (checkBox != null && checkBox.IsChecked.HasValue)
                {
                    if (checkBox.IsChecked.Value)
                    {
                        masks.Add(int.Parse(checkBox.Content.ToString()));
                    }
                }
            }
        }

        private void draw()
        {
            var filteredAtoms = from atom in atoms
                                where masks.Contains(atom.PositionNumber)
                                select atom;
            visualModel.Content = atomBuldier.CreateModel(filteredAtoms, alphaSlider.Value);
        }

        private void SetCamera()
        {
            if (visualModel.Content != null)
            {
                var camera = (OrthographicCamera)viewPort.Camera;
                double xCamera;
                bool firstPostion = xCameraPosition.TryParseDouble(out xCamera);
                double yCamera;
                bool secondPostion = yCameraPosition.TryParseDouble(out yCamera);

                if (firstPostion && secondPostion)
                {
                    Point3D position = new Point3D(xCamera, yCamera, -40);// z doesn't "zoom" for Orthographic camera
                    camera.Position = position;
                }

                double xDirection;
                bool xLook = xLookDirection.TryParseDouble(out xDirection);
                double yDirection;
                bool yLook = yLookDirection.TryParseDouble(out yDirection);
                if (xLook && yLook)
                {
                    Vector3D lookDirection = new Vector3D(xDirection, yDirection, 40);
                    camera.LookDirection = lookDirection;
                }
            }
        }

        private void cameraButton_Click(object sender, RoutedEventArgs e)
        {
            SetCamera();
        }

        private void ScaleAtoms(double radius)
        {
            if (visualModel.Content != null)
            {
                var models = (Model3DGroup)visualModel.Content;
                foreach (var model in models.Children)
                {
                    var transGroup = (Transform3DGroup)model.Transform;
                    var scale = (ScaleTransform3D)transGroup.Children[1];
                    scale.ScaleX = radius;
                    scale.ScaleY = radius;
                    scale.ScaleZ = radius;
                }
            }
        }

        private void alphaSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (visualModel.Content != null)
            {
                var models = (Model3DGroup)visualModel.Content;
                foreach (var model in models.Children)
                {
                    var geoModel = (GeometryModel3D)model;
                    if (geoModel.Material is DiffuseMaterial)
                    {
                        var material = (DiffuseMaterial)geoModel.Material;
                        material.Brush.Opacity = e.NewValue;
                    }
                    if (geoModel.Material is EmissiveMaterial)
                    {
                        var material = (EmissiveMaterial)geoModel.Material;
                        material.Brush.Opacity = e.NewValue;
                    }
                }
            }
        }

        private void saveImageButton_Click(object sender, RoutedEventArgs e)
        {
            SaveRTBAsPNG(System.IO.Path.ChangeExtension(fileName, ".png"), "img");
        }

        private void SaveRTBAsPNG(string filename, string dir)
        {
            RenderTargetBitmap bmp = new RenderTargetBitmap(900, 900, 120, 120, PixelFormats.Pbgra32);
            bmp.Render(viewPort);
            bmp.Render(fileNameLabel); // added label with the file name to the left up corner of the screenshot
            var enc = new System.Windows.Media.Imaging.PngBitmapEncoder();
            enc.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bmp));

            string path = Directory.GetCurrentDirectory();
            var target = System.IO.Path.Combine(path, dir);
            if (!Directory.Exists(target))
            {
                Directory.CreateDirectory(target);
            }

            using (var stm = System.IO.File.Create(System.IO.Path.Combine(target, filename)))
            {
                enc.Save(stm);
            }
        }

        private void maskButton_Checked(object sender, RoutedEventArgs e)
        {
            checkBoxPanel.Visibility = System.Windows.Visibility.Visible;
            Storyboard anim = (Storyboard)this.Resources["maskIn"];
            anim.Begin();
        }

        private void maskButton_Unchecked(object sender, RoutedEventArgs e)
        {
            Storyboard anim = (Storyboard)this.Resources["maskOut"];
            anim.Completed += (sen, evn) => { checkBoxPanel.Visibility = System.Windows.Visibility.Collapsed; };
            anim.Begin();
        }

        private void processFilesButton_Click(object sender, RoutedEventArgs e)
        {
            var dialog = new System.Windows.Forms.FolderBrowserDialog();
            dialog.ShowNewFolderButton = false;
            //dialog.Description = "Choose Directory with .chmc file to generate models";
            //dialog.RootFolder = Environment.SpecialFolder.Startup;
            System.Windows.Forms.DialogResult result = dialog.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                processingLabel.Visibility = System.Windows.Visibility.Visible;
                this.IsEnabled = false;
                processFiles(dialog.SelectedPath);
                //var task = Task.Factory.StartNew(() => processFile(dialog.SelectedPath));
                //task.ContinueWith(new Action<Task>(
                //    (a) => {processingGrid.Visibility = System.Windows.Visibility.Collapsed;}
                //    ));
            }
            this.IsEnabled = true;
            processingLabel.Visibility = System.Windows.Visibility.Collapsed;
        }

        private void processFiles(string folderPath)
        {
            var files = Directory.GetFiles(folderPath);
            var dataFiles = from file in files
                            where System.IO.Path.GetExtension(file) == ".chmc"
                            //orderby file.Length
                            select file;

            foreach (var file in dataFiles)
            {
                fileName = System.IO.Path.GetFileNameWithoutExtension(file);
                fileNameLabel.Content = fileName;
                Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle); // hack to for higher priority rendering :)
                ReadPosition(file);
                parseMasks();
                draw();
                ScaleAtoms(radius);
                SaveRTBAsPNG(System.IO.Path.ChangeExtension(fileName, ".png"), "screens");
            }
        }

        private void scaleTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            var textbox = sender as TextBox;
            bool scaled = textbox.TryParseDouble(out radius);
            if (scaled)
            {
                ScaleAtoms(radius);
            }
        }

        private void xCameraPosition_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCamera();
        }

        private void yCameraPosition_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCamera();
        }

        private void xLookDirection_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCamera();
        }

        private void yLookDirection_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCamera();
        }

        private void zLookDirection_TextChanged(object sender, TextChangedEventArgs e)
        {
            SetCamera();
        }

        private void checkBoxPanel_Click(object sender, RoutedEventArgs e)
        {
            var checkBox = e.Source as CheckBox;
            if (checkBox != null)
            {
                parseMasks();
                draw();
            }
        }

        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.System)
            {
                if (e.SystemKey == Key.LeftAlt)
                {
                    mouseHelper.IsAltDown = true;
                }
            }
        }

        private void Window_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.System)
            {
                if (e.SystemKey == Key.LeftAlt)
                {
                    mouseHelper.IsAltDown = false;
                }
            }
        }
    }
}